package com.lazyboyl.sharding.proxy.net.frontend;

import com.lazyboyl.sharding.proxy.config.ErrorCode;
import com.lazyboyl.sharding.proxy.config.MySqlConfigEnum;
import com.lazyboyl.sharding.proxy.net.entity.ShardingLoginInfo;
import com.lazyboyl.sharding.proxy.net.proto.mysql.*;
import com.lazyboyl.sharding.proxy.net.proto.util.Capabilities;
import com.lazyboyl.sharding.proxy.net.proto.util.RandomUtil;
import com.lazyboyl.sharding.proxy.net.proto.util.SecurityUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;

/**
 * @author linzef
 * @since 2021-06-29
 * 类描述：前端交互的鉴权的通道
 */
public class FrontendAuthenticator extends ChannelHandlerAdapter {

    protected FrontendConnection connection;

    public FrontendAuthenticator(FrontendConnection connection) {
        this.connection = connection;
    }

    private static final Logger logger = LoggerFactory.getLogger(FrontendAuthenticator.class);

    public byte[] seed;

    /**
     * 发送握手包
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        connection.setCtx(ctx);
        // 生成认证数据
        byte[] rand1 = RandomUtil.randomBytes(8);
        byte[] rand2 = RandomUtil.randomBytes(12);

        // 保存认证数据
        byte[] seed = new byte[rand1.length + rand2.length];
        System.arraycopy(rand1, 0, seed, 0, rand1.length);
        System.arraycopy(rand2, 0, seed, rand1.length, rand2.length);
        this.seed = seed;

        byte PROTOCOL_VERSION = 10;

        // 发送握手数据包
        HandshakePacket hs = new HandshakePacket();
        hs.packetId = 0;
        hs.protocolVersion = PROTOCOL_VERSION;
        hs.serverVersion = MySqlConfigEnum.SERVER_VERSION.getStrValue().getBytes(StandardCharsets.UTF_8);
        hs.seed = rand1;
        hs.serverCapabilities = getServerCapabilities();
        hs.serverStatus = 2;
        hs.restOfScrambleBuff = rand2;
        hs.serverCharsetIndex = (byte) (connection.getCharsetIndex() & 0xff);
        // 返回当前的线程的ID，用于kill的时候使用
        hs.threadId = connection.getId();
        hs.write(ctx);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        BinaryPacket bin = (BinaryPacket) msg;
        AuthPacket authPacket = new AuthPacket();
        authPacket.read(bin);
        // 验证账号和密码
        if (!checkPassword(authPacket.password, authPacket.user)) {
            failure(ErrorCode.ER_ACCESS_DENIED_ERROR, "Access denied for user '" + authPacket.user + "'");
            return;
        }
        connection.setShardingProxyUser(connection.getShardingLoginInfo().getShardingProxyUser());
        connection.setShardingProxyDatabaseName(connection.getShardingLoginInfo().getShardingProxyDatabaseName());
        connection.setShardingProxyPass(connection.getShardingLoginInfo().getShardingProxyPass());
        connection.setHost(((InetSocketAddress) ctx.channel().remoteAddress()).getAddress().getHostAddress());
        connection.setPort(((InetSocketAddress) ctx.channel().remoteAddress()).getPort());
        success(ctx);
    }

    private void success(final ChannelHandlerContext ctx) {
        // AUTH_OK , process command
        ctx.pipeline().replace(this, "frontCommandHandler", new FrontendCommandHandler(connection));
        // AUTH_OK is stable
        ByteBuf byteBuf = ctx.alloc().buffer().writeBytes(OkPacket.AUTH_OK);
        // just io , no need thread pool
        ctx.writeAndFlush(byteBuf);
    }

    protected boolean checkPassword(byte[] password, String user) {
        if (password != null && password.length > 0) {
            if (user == null || user.equals("")) {
                return false;
            }
            if (user.equals(connection.getShardingLoginInfo().getShardingProxyUser())) {
                byte[] encryptPass;
                try {
                    encryptPass = SecurityUtil.scramble411(connection.getShardingLoginInfo().getShardingProxyPass().getBytes(), seed);
                } catch (NoSuchAlgorithmException e) {
                    logger.info(e.getMessage());
                    return false;
                }
                if (encryptPass != null && (encryptPass.length == password.length)) {
                    int i = encryptPass.length;
                    while (i-- != 0) {
                        if (encryptPass[i] != password[i]) {
                            return false;
                        }
                    }
                } else {
                    return false;
                }
                return true;
            }
        }
        return false;
    }

    protected void failure(int errno, String info) {
        logger.error(info);
        connection.writeErrMessage((byte) 2, errno, info);
    }

    protected int getServerCapabilities() {
        int flag = 0;
        flag |= Capabilities.CLIENT_LONG_PASSWORD;
        flag |= Capabilities.CLIENT_FOUND_ROWS;
        flag |= Capabilities.CLIENT_LONG_FLAG;
        flag |= Capabilities.CLIENT_CONNECT_WITH_DB;
        // flag |= Capabilities.CLIENT_NO_SCHEMA;
        // flag |= Capabilities.CLIENT_COMPRESS;
        flag |= Capabilities.CLIENT_ODBC;
        // flag |= Capabilities.CLIENT_LOCAL_FILES;
        flag |= Capabilities.CLIENT_IGNORE_SPACE;
        flag |= Capabilities.CLIENT_PROTOCOL_41;
        flag |= Capabilities.CLIENT_INTERACTIVE;
        // flag |= Capabilities.CLIENT_SSL;
        flag |= Capabilities.CLIENT_IGNORE_SIGPIPE;
        flag |= Capabilities.CLIENT_TRANSACTIONS;
        // flag |= ServerDefs.CLIENT_RESERVED;
        flag |= Capabilities.CLIENT_SECURE_CONNECTION;
        return flag;
    }


}
